package lxs.swift.collector.wrap;


import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public interface Option<T> extends Wrapper<T, RuntimeException, Option<T>>, Serializable {
    None<?> NONE = new None<>();

    boolean isSome();

    boolean isNone();

    default <R> Option<R> map(@NotNull Function<T, R> mapping) {
        Objects.requireNonNull(mapping);
        if (isSome()) {
            T v = this.unwrap();
            R r = mapping.apply(v);
            return Option.some(r);
        }
        return Option.none();
    }

    default Trouble<T, RuntimeException> some() {
        if (this.isSome()) {
            return Trouble.ok(this.unwrap());
        }
        return Trouble.err(new NullPointerException("Option is None"));
    }

    default Trouble<Void, RuntimeException> non() {
        if (this.isSome()) {
            return Trouble.err(new RuntimeException("Except None,but Some"));
        }
        return Trouble.nil();
    }

    default Option<T> is(@NotNull Predicate<T> predicate) {
        Objects.requireNonNull(predicate);
        if (!this.isSome()) return Option.none();
        T v = this.unwrap();
        if (predicate.test(v)) {
            return this;
        }
        return Option.none();
    }

    default Option<T> replace(boolean bool, @NotNull Supplier<T> supplier) {
        Objects.requireNonNull(supplier);
        if (!bool || !isSome()) return this;
        return Option.some(supplier.get());
    }

    @Override
    default Option<T> copy() {
        if (isSome()) {
            return Option.some(this.unwrap());
        }
        return new None<>();
    }

    @Override
    default T unwrapOr(@NotNull Supplier<T> supplier) {
        Objects.requireNonNull(supplier);
        if (this.isSome()) return this.unwrap();
        return supplier.get();
    }

    @Override
    default Option<T> and(@NotNull Option<T> wrapper) {
        Objects.requireNonNull(wrapper);
        if (this.isSome()) {
            return wrapper;
        }
        return this;
    }

    @Override
    default Option<T> or(@NotNull Option<T> wrapper) {
        Objects.requireNonNull(wrapper);
        if (this.isSome()) return this;
        return wrapper;
    }

    default <RR> RR matchMap(Function<T, RR> some, Supplier<RR> none) {
        if (this.isSome()) {
            return some.apply(unwrap());
        }
        return none.get();
    }

    default void matchRun(Consumer<T> some, Runnable none) {
        if (this.isSome()) {
            some.accept(unwrap());
            return;
        }
        none.run();
    }

    default <RR> RR selected(RR some, RR none) {
        if (this.isSome()) {
            return some;
        }
        return none;
    }

    static <T> Option<T> any(T... values) {
        if (values == null) return Option.none();
        for (T value : values) {
            if (value != null) return Option.some(value);
        }
        return Option.none();
    }

    static <T> Option<T> of(@Nullable T value) {
        if (value == null) return Option.none();
        return Option.some(value);
    }

    static <T> Option<T> of(@NotNull Supplier<T> supplier) {
        Objects.requireNonNull(supplier);
        T value = supplier.get();
        return Option.some(value);
    }

    static <T> Option<T> some(@NotNull T value) {
        Objects.requireNonNull(value);
        return new Some<>(value);
    }

    static <T> Option<T> none() {
        return ((Option<T>) NONE);
    }

    class Some<T> implements Option<T> {
        public final T value;

        private Some(T value) {
            this.value = value;
        }

        @Override
        public boolean isSome() {
            return true;
        }

        @Override
        public boolean isNone() {
            return false;
        }

        @Override
        public T unwrap() throws RuntimeException {
            return value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Some<?> some = (Some<?>) o;
            return Objects.equals(value, some.value);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        @Override
        public String toString() {
            String typeName = value.getClass().getTypeName();
            if (value instanceof CharSequence) {
                return typeName + ":Option.Some(\"" + value + "\")";
            }
            return typeName + ":Option.Some(" + value + ")";
        }
    }

    class None<T> implements Option<T> {
        private None() {
        }

        @Override
        public boolean isSome() {
            return false;
        }

        @Override
        public boolean isNone() {
            return true;
        }

        @Override
        public T unwrap() throws RuntimeException {
            throw new NullPointerException("called `Option::unwrap()` on a `None` value");
        }

        @Override
        public String toString() {
            return "Option.None";
        }
    }


}
